# ICEMGenerator - 高效的ICEM CFD网格划分Replay Script生成工具
# 模块： Body类，用于创建体，其所创建的体大部分为中间体，只有设定为final的体才是最终用于生成Body的那个体
# 作者： weixc
# 邮箱： 12028045 [at] zju.edu.cn
# 项目主页： https://gitee.com/weixinchong/icemgenerator

import numpy as np
from collections.abc import Iterable
from .point import Point
from .curve import Curve, Line, Ring, Arc
from .surface import Surface, check_inclusion
from .param_check import check_axis, ListSizeError

class Body():
    '''创建一个体'''

    members = []  #现有体的成员列表
    final_members = []  #最终用于生成Body的体

    def final_members_replay() -> str:
        '''生成最终Body的Replay Script
        :return: 生成的Replay Script'''
        text = ''
        for b in Body.final_members:
            text += b.replay() + '\n'
        return text

    def find_member(surfaces: Surface):
        '''查找指定的体是否已经定义过
        :param surfaces: 要查找的体内的面
        :return (Body | None): 找到已定义的体时，返回该体；否则为None'''
        for b in Body.members:
            if len(surfaces) != len(b.surfaces):  #当两个体的面数量不一致时，两个体显然不是同一个体
                continue
            body_is_same = True
            for i in range(surfaces):
                if surfaces[i] not in b.surfaces[i]:
                    body_is_same = False
                    break
            if body_is_same:
                return b

    def createFromEntireModel(name: str):
        '''从整个模型创建一个体
        :param name: 体的名称
        :return: 创建的体
        [特别说明]
        从整个模型创建的体，已经预设为最终体，故不需要再调用setAsFinal方法'''
        body = Body([], name)
        body.fromEntireModel = True
        body.setAsFinal()
        return body

    def __init__(self, surfaces: Iterable, name: str):
        '''创建由一系列面构成的体
        :param surfaces: 构成体的面
        :param name: 体的名称'''
        self.surfaces = sorted(surfaces, key = lambda s: s.sId)
        self.name = name
        self.fromEntireModel = False
        # 将各个面的线、点整合到体的线、点中
        self.curves = []
        self.points = []
        for s in self.surfaces:
            for c in s.curves:
                if c not in self.curves:
                    self.curves.append(c)
                    for p in c.points:
                        if p not in self.points:
                            self.points.append(p)
        self.curves = sorted(self.curves, key = lambda c: c.cId)
        self.points = sorted(self.points, key = lambda p: p.pId)
        Body.members.append(self)  #将当前体增加到成员列表

    def addSurface(self, surface: Surface | Iterable):
        '''向当前体中加入新的面
        :param surface: 新加入的面，可以是一个或多个'''
        if isinstance(surface, Iterable):
            self.surfaces += surface
        else:
            self.surfaces.append(surface)
            surface = (surface,)
        # 将各个面的线、点整合到体的线、点中
        for s in surface:
            for c in s.curves:
                if c not in self.curves:
                    self.curves.append(c)
                    for p in c.points:
                        if p not in self.points:
                            self.points.append(p)
        self.surfaces = sorted(self.surfaces, key = lambda s: s.sId)
        self.curves = sorted(self.curves, key = lambda c: c.cId)
        self.points = sorted(self.points, key = lambda p: p.pId)

    def __str__(self):
        return self.name

    def __repr__(self):
        return self.__str__()

    def setName(self, name: str):
        '''设置体的名称
        :param name: 要设定的体名称'''
        self.name = name

    def setAsFinal(self):
        '''将当前体设为最终要生成的体'''
        Body.final_members.append(self)

    def replay(self) -> str:
        '''生成面的Replay Script
        :return: 生成的Replay Script'''
        if self.fromEntireModel:
            return 'ic_geo_build_bodies %s 0 0 0 1 {} 0 0' % self.name
        else:
            srf_expr = ' '.join([str(si) for si in self.surfaces])
            return 'ic_geo_create_body {%s} {} %s' % (srf_expr, self.name)

    def info(self, single: bool = False) -> str:
        '''获得体的信息
        [参数说明]
        :param single: 是否输出单一信息:
            为True时: 仅输出体的信息；
            为False时: 还会输出每个面、每条线、每个点的信息
        :return: 体的信息'''
        # 先输出体的信息
        text = 'Body %s' % self.name
        srf_expr = ' '.join([str(si) for si in self.surfaces])
        text += ': (%s)' % srf_expr
        if not single:
            # 输出面的信息
            text += '\n[Surfaces Included]'
            for s in self.surfaces:
                text += '\n' + s.info(single = True)
            # 输出线的信息
            text += '\n[Curves Included]'
            for c in self.curves:
                text += '\n' + c.info(single = True)
            # 输出点的信息
            text += '\n[Points Included]'
            for p in self.points:
                text += '\n' + p.info()
        return text

    def getSurface(self, name: str) -> Surface | None:
        '''获得体中指定名称的面
        :param name: 面的名称
        :return: 找到指定名称的面时，返回该面；否则返回None'''
        for s in self.surfaces:
            if s.name == name:
                return s

    def getCurve(self, name: str) -> Curve | None:
        '''获得体中指定名称的线
        :param name: 线的名称
        :return: 找到指定名称的线时，返回该线；否则返回None'''
        for c in self.curves:
            if c.name == name:
                return c

    def getPoint(self, name: str) -> Point | None:
        '''获得体中指定名称的点
        :param name: 点的名称
        :return: 找到指定名称的点时，返回该点；否则返回None'''
        for p in self.points:
            if p.name == name:
                return p

    def join(self, obj):
        '''将客体obj合并到当前体中
        :param obj (Body或list|tuple<Body>): 一个或多个体，当要并入的体为多个时，应注意并入的体应该是并行的，而彼此之间不存在依赖关系'''
        if type(obj) != list and type(obj) != tuple:
            obj = (obj,)
        # 找出当前体的所有平面
        srf_planes = list(filter(lambda s: s.is_plane, self.surfaces))
        for o in obj:
            # 将体o中的点、线与当前体的点、线合并
            for p in o.points:
                if p not in self.points:
                    self.points.append(p)
            for c in o.curves:
                if c not in self.curves:
                    self.curves.append(c)
            # 找出体o与当前体重合的客面，并用不重合的客面切割当前体的主面
            for so in o.surfaces:
                surf_be_cut = None
                if so.is_plane:
                    for ss in srf_planes:
                        if so == ss:  #如果两个面就是同一个面，则两个面必然重合
                            surf_be_cut = ss
                            break  #找出一个重合的面即可终止，下同
                        elif np.sum(so.direction == ss.direction) == 3:  #判断平面的法向量是否共线
                            if so.direction[0] == 1 and so.points[0].X[0] == ss.points[0].X[0]:  #法向量为x方向时，比较x值
                                if check_inclusion(ss, so, 'x'):  #判断客面是否在主面内，下同
                                    surf_be_cut = ss
                                    break
                            elif so.direction[1] == 1 and so.points[0].X[1] == ss.points[0].X[1]:  #法向量为y方向时，比较y值
                                if check_inclusion(ss, so, 'y'):
                                    surf_be_cut = ss
                                    break
                            elif so.direction[2] == 1 and so.points[0].X[2] == ss.points[0].X[2]:  #法向量为z方向时，比较z值
                                if check_inclusion(ss, so, 'z'):
                                    surf_be_cut = ss
                                    break
                if not surf_be_cut is None:  #若发现主体中有需要用so客面切割的面，则切割它
                    surf_be_cut.cutBy(so)
                else:
                    if so not in self.surfaces:
                        self.surfaces.append(so)

def create_cubic(x1: float, x2: float, y1: float, y2: float, z1: float, z2: float, \
            left_filled: bool = True, right_filled: bool = True, bottom_filled: bool = True, \
            top_filled: bool = True, back_filled: bool = True, front_filled: bool = True, name: str = 'CUBIC1') -> Body:
    '''创建一个长方体
    :params x1, x2, y1, y2, x3, y3: 表明长方体由(x1,y1,z1)、(x2,y2,z2)围成
    :params left_filled, right_filled, bottom_filled, top_filled, back_filled, front_filled: 是否绘制圆柱的底面、顶面，当需要将立方体的其中一个或几个面镂空时，可设定为False
    :param name: 体的名称
    :return: 创建的长方体'''
    # 创建点
    pnt_lbb = Point.create(x1, y1, z1, 'left_bottom_back')
    pnt_rbb = Point.create(x2, y1, z1, 'right_bottom_back')
    pnt_ltb = Point.create(x1, y2, z1, 'left_top_back')
    pnt_rtb = Point.create(x2, y2, z1, 'right_top_back')
    pnt_lbf = Point.create(x1, y1, z2, 'left_bottom_front')
    pnt_rbf = Point.create(x2, y1, z2, 'right_bottom_front')
    pnt_ltf = Point.create(x1, y2, z2, 'left_top_front')
    pnt_rtf = Point.create(x2, y2, z2, 'right_top_front')
    # 创建线
    crv_lob = Line.create(pnt_lbb, pnt_ltb, 'left_o_back')
    crv_rob = Line.create(pnt_rbb, pnt_rtb, 'right_o_back')
    crv_obb = Line.create(pnt_lbb, pnt_rbb, 'o_bottom_back')
    crv_otb = Line.create(pnt_ltb, pnt_rtb, 'o_top_back')
    crv_lof = Line.create(pnt_lbf, pnt_ltf, 'left_o_back')
    crv_rof = Line.create(pnt_rbf, pnt_rtf, 'right_o_back')
    crv_obf = Line.create(pnt_lbf, pnt_rbf, 'o_bottom_back')
    crv_otf = Line.create(pnt_ltf, pnt_rtf, 'o_top_back')
    crv_lbo = Line.create(pnt_lbb, pnt_lbf, 'left_bottom_o')
    crv_lto = Line.create(pnt_ltb, pnt_ltf, 'left_top_o')
    crv_rbo = Line.create(pnt_rbb, pnt_rbf, 'right_bottom_o')
    crv_rto = Line.create(pnt_rtb, pnt_rtf, 'right_top_o')
    # 创建面
    srfs = []
    if left_filled:
        srfs.append(Surface.create((crv_lbo, crv_lto, crv_lob, crv_lof), 'left'))
    if right_filled:
        srfs.append(Surface.create((crv_rbo, crv_rto, crv_rob, crv_rof), 'right'))
    if bottom_filled:
        srfs.append(Surface.create((crv_lbo, crv_rbo, crv_obb, crv_obf), 'bottom'))
    if top_filled:
        srfs.append(Surface.create((crv_lto, crv_rto, crv_otb, crv_otf), 'top'))
    if back_filled:
        srfs.append(Surface.create((crv_lob, crv_rob, crv_obb, crv_otb), 'back'))
    if front_filled:
        srfs.append(Surface.create((crv_lof, crv_rof, crv_obf, crv_otf), 'front'))
    # 创建体
    body = Body(srfs, name)
    return body

def create_cylinder(r: float | Iterable, h: float | Iterable, \
            x: float = 0, y: float = 0, z: float = 0, axis: str = 'z', \
            bottom_filled: bool = True, top_filled: bool = True, name: str = 'CYLINDER1') -> Body:
    '''创建一个单级或多级圆柱体
    :param r: 圆柱半径，对于多级圆柱，每个ri表示一级的半径
    :param h: 圆柱高度，对于多级圆柱，每个hi表示一级的高度，可以取负值，表示沿负半轴方向
    :params x, y, z: 圆柱的底面圆心坐标
    :param axis: 圆柱的轴向，可选值为x、y、z
    :params bottom_filled, top_filled: 是否绘制圆柱的底面、顶面，当绘制空心圆筒时，可设定为False
    :param name: 圆柱体的名称
    :return: 创建的圆柱体'''
    check_axis(axis)
    if not isinstance(r, Iterable):
        r = (r,)
        h = (h,)
    elif len(r) != len(h):
        raise ListSizeError('各级半径ri与高度hi的数量应保持一致')
    nc = len(r)
    ring = [None] * nc
    srf_side = [None] * nc
    if nc > 1:
        srf_annulus = [None] * (nc - 1)
    else:
        srf_annulus = []
    # 创建线（各级圆环）
    if axis == 'x':
        x1 = x
    elif axis == 'y':
        y1 = y
    elif axis == 'z':
        z1 = z
    for i in range(nc):
        if axis == 'x':
            ring[i] = [Ring.create(r[i], x1, y, z, axis, 'ring%d-tail' % i), 
                    Ring.create(r[i], x1 + h[i], y, z, axis, 'ring%d-head' % i)]
            x1 += h[i]
        elif axis == 'y':
            ring[i] = [Ring.create(r[i], x, y1, z, axis, 'ring%d-tail' % i),
                    Ring.create(r[i], x, y1 + h[i], z, axis, 'ring%d-head' % i)]
            y1 += h[i]
        elif axis == 'z':
            ring[i] = [Ring.create(r[i], x, y, z1, axis, 'ring%d-tail' % i), 
                    Ring.create(r[i], x, y, z1 + h[i], axis, 'ring%d-head' % i)]
            z1 += h[i]
    # 创建面
    srf_list = []
    # (1)上下底面
    if bottom_filled:
        srf_list.append(Surface.create((ring[0][0],), 'circle_bottom'))
    if top_filled:
        srf_list.append(Surface.create((ring[-1][1],), 'circle_top'))
    # (2)各级侧面
    for i in range(nc):
        srf_side[i] = Surface.create((ring[i][0], ring[i][1]), 'side%d' % i, method = 'loft')
    srf_list += srf_side
    # (3)夹在两级之间的圆环面
    if nc > 1:
        for i in range(nc - 1):
            srf_annulus[i] = Surface.create((ring[i][1], ring[i+1][0]), 'annulus%d' % i)
    srf_list += srf_annulus
    # 创建体
    body = Body(srf_list, name)
    return body

def create_circular(r: Iterable, h: Iterable, x: float = 0, y: float = 0, z: float = 0, \
            axis: str = 'z', bottom_filled: bool = True, top_filled: bool = True, name: str = 'CIRCULARS1') -> Body:
    '''创建一个单级或多级回转体
    :param r: 由始端到末端每一处圆环的半径
    :param h: 每两个圆环之间夹的回转体高度，故n(h)=n(r)-1
    :param x, y, z: 回转体的底面圆心坐标
    :param axis: 回转体的轴向，可选值为x、y、z
    :param bottom_filled, top_filled: 是否绘制回转体的底面、顶面；当绘制空心圆筒时，可设定为False
    :param name: 回转体的名称
    :return: 创建的回转体'''
    check_axis(axis)
    nc = len(r)
    ring = [None] * nc
    side = [None] * (nc - 1)
    if len(r) != len(h) + 1:
        raise ListSizeError('各级半径ri的总数应该是高度hi的总数加1')
    # 创建面
    # (1)底圆环
    ring[0] = Ring.create(r[0], x, y, z, axis, 'ring0')
    # (2)依次向上的圆环
    if axis == 'x':
        x1 = x
    elif axis == 'y':
        y1 = y
    elif axis == 'z':
        z1 = z
    for i in range(1, nc):
        if axis == 'x':
            x1 += h[i - 1]
            ring[i] = Ring.create(r[i], x1, y, z, axis, 'ring%d' % i)
        elif axis == 'y':
            y1 += h[i - 1]
            ring[i] = Ring.create(r[i], x, y1, z, axis, 'ring%d' % i)
        elif axis == 'z':
            z1 += h[i - 1]
            ring[i] = Ring.create(r[i], x, y, z1, axis, 'ring%d' % i)
    # 创建面
    srf_list = []
    # (1)上下底面
    if bottom_filled:
        srf_list.append(Surface.create((ring[0],), 'circle_bottom'))
    if top_filled:
        srf_list.append(Surface.create((ring[-1],), 'circle_top'))
    # (2)依次向上的圆环包围得到的侧面
    for i in range(1, nc):
        side[i - 1] = Surface.create((ring[i - 1], ring[i]), 'side%d' % i, method = 'loft')
    srf_list += side
    # 创建体
    body = Body(srf_list, name)
    return body

def create_elbow_pipe(rb: float, rt: float, xb: float = 0, yb: float = 0, zb: float = 0, \
            xt: float = 0, yt: float = 0, zt: float = 0, axis_b: str = 'z', axis_t: str = 'z', \
            angle_start_t: float = 0, angle_end_t: float = 90, name: str = None) -> Body:
    '''创建一个弯管，该弯管以一个圆环为底面，一条弧线为弯曲轨迹
    b: base，表示底面
    t: track，代表弯管的弯曲轨迹
    :param rb: 底面的半径
    :param rt: 弧线的半径
    :params xb, yb, zb: 底面的圆心
    :params xt, yt, zt: 弧线的圆心
    :param axis_b: 底面的轴向，可选值为x、y、z
    :param axis_t: 弧线的轴向，可选值为x、y、z
    :param angle_start_t,angle_end_t: 表示弧线的角度从angle_start_t到angle_end_t
    :param name: 弯管的名称
    :return: 创建的弯管'''
    check_axis(axis_b)
    check_axis(axis_t)
    base = Ring.create(rb, xb, yb, zb, axis_b, name = 'base')
    track = Arc.create(rt, xt, yt, zt, axis_t, angle_start_t, angle_end_t, name = 'track')
    srf = Surface.create((track, base), method = 'sweep')
    elbow_pipe = Body((srf,), name = name)
    return elbow_pipe